{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "whjsJasuhstV"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jeffheaton/app_generative_ai/blob/main/t81_559_class_06_3_embeddings.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "euOZxlIMhstX"
   },
   "source": [
    "# T81-559: Applications of Generative Artificial Intelligence\n",
    "**Module 6: Retrieval-Augmented Generation (RAG)**\n",
    "* Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
    "* For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d4Yov72PhstY"
   },
   "source": [
    "# Module 6 Material\n",
    "\n",
    "* Part 6.1: Introduction to Retrieval-Augmented Generation (RAG) [[Video]](https://www.youtube.com/watch?v=qA52K0K181Q) [[Notebook]](t81_559_class_06_1_rag.ipydb)\n",
    "* Part 6.2: Introduction to ChromaDB [[Video]](https://www.youtube.com/watch?v=R53lo4sevLQ) [[Notebook]](t81_559_class_06_2_chromadb.ipynb)\n",
    "* **Part 6.3: Understanding Embeddings** [[Video]](https://www.youtube.com/watch?v=Tq82Gl2ZZNM) [[Notebook]](t81_559_class_06_3_embeddings.ipynb)\n",
    "* Part 6.4: Question Answering Over Documents [[Video]](https://www.youtube.com/watch?v=hCwL_lW-gP0) [[Notebook]](t81_559_class_06_4_qa.ipynb)\n",
    "* Part 6.5: Embedding Databases [[Video]](https://www.youtube.com/watch?v=BG2gT4uYxhM) [[Notebook]](t81_559_class_06_5_embed_db.ipynb)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AcAUP0c3hstY"
   },
   "source": [
    "# Google CoLab Instructions\n",
    "\n",
    "The following code ensures that Google CoLab is running and maps Google Drive if needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "xsI496h5hstZ",
    "outputId": "24f19116-7ed8-44a0-f4ab-e0b09fd5b365"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: using Google CoLab\n",
      "Collecting langchain\n",
      "  Downloading langchain-0.2.5-py3-none-any.whl (974 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m974.6/974.6 kB\u001b[0m \u001b[31m4.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting langchain_openai\n",
      "  Downloading langchain_openai-0.1.9-py3-none-any.whl (40 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m40.3/40.3 kB\u001b[0m \u001b[31m2.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting pypdf\n",
      "  Downloading pypdf-4.2.0-py3-none-any.whl (290 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m290.4/290.4 kB\u001b[0m \u001b[31m15.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting chromadb\n",
      "  Downloading chromadb-0.5.3-py3-none-any.whl (559 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m559.5/559.5 kB\u001b[0m \u001b[31m20.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.1)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.30)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.9.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
      "Collecting langchain-core<0.3.0,>=0.2.7 (from langchain)\n",
      "  Downloading langchain_core-0.2.9-py3-none-any.whl (321 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m321.8/321.8 kB\u001b[0m \u001b[31m17.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting langchain-text-splitters<0.3.0,>=0.2.0 (from langchain)\n",
      "  Downloading langchain_text_splitters-0.2.1-py3-none-any.whl (23 kB)\n",
      "Collecting langsmith<0.2.0,>=0.1.17 (from langchain)\n",
      "  Downloading langsmith-0.1.81-py3-none-any.whl (127 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m127.1/127.1 kB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.25.2)\n",
      "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.7.4)\n",
      "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.31.0)\n",
      "Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (8.4.1)\n",
      "Collecting openai<2.0.0,>=1.26.0 (from langchain_openai)\n",
      "  Downloading openai-1.35.3-py3-none-any.whl (327 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m327.4/327.4 kB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting tiktoken<1,>=0.7 (from langchain_openai)\n",
      "  Downloading tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m13.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: typing_extensions>=4.0 in /usr/local/lib/python3.10/dist-packages (from pypdf) (4.12.2)\n",
      "Requirement already satisfied: build>=1.0.3 in /usr/local/lib/python3.10/dist-packages (from chromadb) (1.2.1)\n",
      "Collecting chroma-hnswlib==0.7.3 (from chromadb)\n",
      "  Downloading chroma_hnswlib-0.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.4 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.4/2.4 MB\u001b[0m \u001b[31m15.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting fastapi>=0.95.2 (from chromadb)\n",
      "  Downloading fastapi-0.111.0-py3-none-any.whl (91 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m92.0/92.0 kB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting uvicorn[standard]>=0.18.3 (from chromadb)\n",
      "  Downloading uvicorn-0.30.1-py3-none-any.whl (62 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m62.4/62.4 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting posthog>=2.4.0 (from chromadb)\n",
      "  Downloading posthog-3.5.0-py2.py3-none-any.whl (41 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m41.3/41.3 kB\u001b[0m \u001b[31m2.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting onnxruntime>=1.14.1 (from chromadb)\n",
      "  Downloading onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.8 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m6.8/6.8 MB\u001b[0m \u001b[31m42.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting opentelemetry-api>=1.2.0 (from chromadb)\n",
      "  Downloading opentelemetry_api-1.25.0-py3-none-any.whl (59 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m59.9/59.9 kB\u001b[0m \u001b[31m5.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting opentelemetry-exporter-otlp-proto-grpc>=1.2.0 (from chromadb)\n",
      "  Downloading opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl (18 kB)\n",
      "Collecting opentelemetry-instrumentation-fastapi>=0.41b0 (from chromadb)\n",
      "  Downloading opentelemetry_instrumentation_fastapi-0.46b0-py3-none-any.whl (11 kB)\n",
      "Collecting opentelemetry-sdk>=1.2.0 (from chromadb)\n",
      "  Downloading opentelemetry_sdk-1.25.0-py3-none-any.whl (107 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m107.0/107.0 kB\u001b[0m \u001b[31m9.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: tokenizers>=0.13.2 in /usr/local/lib/python3.10/dist-packages (from chromadb) (0.19.1)\n",
      "Collecting pypika>=0.48.9 (from chromadb)\n",
      "  Downloading PyPika-0.48.9.tar.gz (67 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.3/67.3 kB\u001b[0m \u001b[31m5.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25h  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
      "  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
      "  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
      "Requirement already satisfied: tqdm>=4.65.0 in /usr/local/lib/python3.10/dist-packages (from chromadb) (4.66.4)\n",
      "Collecting overrides>=7.3.1 (from chromadb)\n",
      "  Downloading overrides-7.7.0-py3-none-any.whl (17 kB)\n",
      "Requirement already satisfied: importlib-resources in /usr/local/lib/python3.10/dist-packages (from chromadb) (6.4.0)\n",
      "Requirement already satisfied: grpcio>=1.58.0 in /usr/local/lib/python3.10/dist-packages (from chromadb) (1.64.1)\n",
      "Collecting bcrypt>=4.0.1 (from chromadb)\n",
      "  Downloading bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl (283 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m283.7/283.7 kB\u001b[0m \u001b[31m25.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: typer>=0.9.0 in /usr/local/lib/python3.10/dist-packages (from chromadb) (0.12.3)\n",
      "Collecting kubernetes>=28.1.0 (from chromadb)\n",
      "  Downloading kubernetes-30.1.0-py2.py3-none-any.whl (1.7 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.7/1.7 MB\u001b[0m \u001b[31m58.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting mmh3>=4.0.1 (from chromadb)\n",
      "  Downloading mmh3-4.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (67 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.6/67.6 kB\u001b[0m \u001b[31m8.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting orjson>=3.9.12 (from chromadb)\n",
      "  Downloading orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (144 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m145.0/145.0 kB\u001b[0m \u001b[31m16.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting httpx>=0.27.0 (from chromadb)\n",
      "  Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m3.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (23.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
      "Requirement already satisfied: packaging>=19.1 in /usr/local/lib/python3.10/dist-packages (from build>=1.0.3->chromadb) (24.1)\n",
      "Requirement already satisfied: pyproject_hooks in /usr/local/lib/python3.10/dist-packages (from build>=1.0.3->chromadb) (1.1.0)\n",
      "Requirement already satisfied: tomli>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from build>=1.0.3->chromadb) (2.0.1)\n",
      "Collecting starlette<0.38.0,>=0.37.2 (from fastapi>=0.95.2->chromadb)\n",
      "  Downloading starlette-0.37.2-py3-none-any.whl (71 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m71.9/71.9 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting fastapi-cli>=0.0.2 (from fastapi>=0.95.2->chromadb)\n",
      "  Downloading fastapi_cli-0.0.4-py3-none-any.whl (9.5 kB)\n",
      "Requirement already satisfied: jinja2>=2.11.2 in /usr/local/lib/python3.10/dist-packages (from fastapi>=0.95.2->chromadb) (3.1.4)\n",
      "Collecting python-multipart>=0.0.7 (from fastapi>=0.95.2->chromadb)\n",
      "  Downloading python_multipart-0.0.9-py3-none-any.whl (22 kB)\n",
      "Collecting ujson!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,>=4.0.1 (from fastapi>=0.95.2->chromadb)\n",
      "  Downloading ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (53 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m53.6/53.6 kB\u001b[0m \u001b[31m7.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting email_validator>=2.0.0 (from fastapi>=0.95.2->chromadb)\n",
      "  Downloading email_validator-2.2.0-py3-none-any.whl (33 kB)\n",
      "Requirement already satisfied: anyio in /usr/local/lib/python3.10/dist-packages (from httpx>=0.27.0->chromadb) (3.7.1)\n",
      "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx>=0.27.0->chromadb) (2024.6.2)\n",
      "Collecting httpcore==1.* (from httpx>=0.27.0->chromadb)\n",
      "  Downloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: idna in /usr/local/lib/python3.10/dist-packages (from httpx>=0.27.0->chromadb) (3.7)\n",
      "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from httpx>=0.27.0->chromadb) (1.3.1)\n",
      "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx>=0.27.0->chromadb)\n",
      "  Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (1.16.0)\n",
      "Requirement already satisfied: python-dateutil>=2.5.3 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (2.8.2)\n",
      "Requirement already satisfied: google-auth>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (2.27.0)\n",
      "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (1.8.0)\n",
      "Requirement already satisfied: requests-oauthlib in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (1.3.1)\n",
      "Requirement already satisfied: oauthlib>=3.2.2 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (3.2.2)\n",
      "Requirement already satisfied: urllib3>=1.24.2 in /usr/local/lib/python3.10/dist-packages (from kubernetes>=28.1.0->chromadb) (2.0.7)\n",
      "Collecting jsonpatch<2.0,>=1.33 (from langchain-core<0.3.0,>=0.2.7->langchain)\n",
      "  Downloading jsonpatch-1.33-py2.py3-none-any.whl (12 kB)\n",
      "Collecting coloredlogs (from onnxruntime>=1.14.1->chromadb)\n",
      "  Downloading coloredlogs-15.0.1-py2.py3-none-any.whl (46 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m6.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: flatbuffers in /usr/local/lib/python3.10/dist-packages (from onnxruntime>=1.14.1->chromadb) (24.3.25)\n",
      "Requirement already satisfied: protobuf in /usr/local/lib/python3.10/dist-packages (from onnxruntime>=1.14.1->chromadb) (3.20.3)\n",
      "Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from onnxruntime>=1.14.1->chromadb) (1.12.1)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.26.0->langchain_openai) (1.7.0)\n",
      "Collecting deprecated>=1.2.6 (from opentelemetry-api>=1.2.0->chromadb)\n",
      "  Downloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)\n",
      "Requirement already satisfied: importlib-metadata<=7.1,>=6.0 in /usr/local/lib/python3.10/dist-packages (from opentelemetry-api>=1.2.0->chromadb) (7.1.0)\n",
      "Requirement already satisfied: googleapis-common-protos~=1.52 in /usr/local/lib/python3.10/dist-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb) (1.63.1)\n",
      "Collecting opentelemetry-exporter-otlp-proto-common==1.25.0 (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb)\n",
      "  Downloading opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl (17 kB)\n",
      "Collecting opentelemetry-proto==1.25.0 (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb)\n",
      "  Downloading opentelemetry_proto-1.25.0-py3-none-any.whl (52 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m52.5/52.5 kB\u001b[0m \u001b[31m6.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting opentelemetry-instrumentation-asgi==0.46b0 (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb)\n",
      "  Downloading opentelemetry_instrumentation_asgi-0.46b0-py3-none-any.whl (14 kB)\n",
      "Collecting opentelemetry-instrumentation==0.46b0 (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb)\n",
      "  Downloading opentelemetry_instrumentation-0.46b0-py3-none-any.whl (29 kB)\n",
      "Collecting opentelemetry-semantic-conventions==0.46b0 (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb)\n",
      "  Downloading opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl (130 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m130.5/130.5 kB\u001b[0m \u001b[31m16.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting opentelemetry-util-http==0.46b0 (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb)\n",
      "  Downloading opentelemetry_util_http-0.46b0-py3-none-any.whl (6.9 kB)\n",
      "Requirement already satisfied: setuptools>=16.0 in /usr/local/lib/python3.10/dist-packages (from opentelemetry-instrumentation==0.46b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb) (67.7.2)\n",
      "Requirement already satisfied: wrapt<2.0.0,>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from opentelemetry-instrumentation==0.46b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb) (1.14.1)\n",
      "Collecting asgiref~=3.0 (from opentelemetry-instrumentation-asgi==0.46b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb)\n",
      "  Downloading asgiref-3.8.1-py3-none-any.whl (23 kB)\n",
      "Collecting monotonic>=1.5 (from posthog>=2.4.0->chromadb)\n",
      "  Downloading monotonic-1.6-py2.py3-none-any.whl (8.2 kB)\n",
      "Collecting backoff>=1.10.0 (from posthog>=2.4.0->chromadb)\n",
      "  Downloading backoff-2.2.1-py3-none-any.whl (15 kB)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.7.0)\n",
      "Requirement already satisfied: pydantic-core==2.18.4 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.18.4)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain_openai) (2024.5.15)\n",
      "Requirement already satisfied: huggingface-hub<1.0,>=0.16.4 in /usr/local/lib/python3.10/dist-packages (from tokenizers>=0.13.2->chromadb) (0.23.4)\n",
      "Requirement already satisfied: click>=8.0.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.9.0->chromadb) (8.1.7)\n",
      "Requirement already satisfied: shellingham>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.9.0->chromadb) (1.5.4)\n",
      "Requirement already satisfied: rich>=10.11.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.9.0->chromadb) (13.7.1)\n",
      "Collecting httptools>=0.5.0 (from uvicorn[standard]>=0.18.3->chromadb)\n",
      "  Downloading httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (341 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m341.4/341.4 kB\u001b[0m \u001b[31m21.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting python-dotenv>=0.13 (from uvicorn[standard]>=0.18.3->chromadb)\n",
      "  Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)\n",
      "Collecting uvloop!=0.15.0,!=0.15.1,>=0.14.0 (from uvicorn[standard]>=0.18.3->chromadb)\n",
      "  Downloading uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.4 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.4/3.4 MB\u001b[0m \u001b[31m86.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting watchfiles>=0.13 (from uvicorn[standard]>=0.18.3->chromadb)\n",
      "  Downloading watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m57.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting websockets>=10.4 (from uvicorn[standard]>=0.18.3->chromadb)\n",
      "  Downloading websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (130 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m130.2/130.2 kB\u001b[0m \u001b[31m15.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio->httpx>=0.27.0->chromadb) (1.2.1)\n",
      "Collecting dnspython>=2.0.0 (from email_validator>=2.0.0->fastapi>=0.95.2->chromadb)\n",
      "  Downloading dnspython-2.6.1-py3-none-any.whl (307 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m307.7/307.7 kB\u001b[0m \u001b[31m31.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: cachetools<6.0,>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (5.3.3)\n",
      "Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.10/dist-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (0.4.0)\n",
      "Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.10/dist-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (4.9)\n",
      "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb) (3.15.1)\n",
      "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers>=0.13.2->chromadb) (2023.6.0)\n",
      "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.10/dist-packages (from importlib-metadata<=7.1,>=6.0->opentelemetry-api>=1.2.0->chromadb) (3.19.2)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2>=2.11.2->fastapi>=0.95.2->chromadb) (2.1.5)\n",
      "Collecting jsonpointer>=1.9 (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.7->langchain)\n",
      "  Downloading jsonpointer-3.0.0-py2.py3-none-any.whl (7.6 kB)\n",
      "Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.10/dist-packages (from rich>=10.11.0->typer>=0.9.0->chromadb) (3.0.0)\n",
      "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.10/dist-packages (from rich>=10.11.0->typer>=0.9.0->chromadb) (2.16.1)\n",
      "Collecting humanfriendly>=9.1 (from coloredlogs->onnxruntime>=1.14.1->chromadb)\n",
      "  Downloading humanfriendly-10.0-py2.py3-none-any.whl (86 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m11.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: mpmath<1.4.0,>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from sympy->onnxruntime>=1.14.1->chromadb) (1.3.0)\n",
      "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.10/dist-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer>=0.9.0->chromadb) (0.1.2)\n",
      "Requirement already satisfied: pyasn1<0.7.0,>=0.4.6 in /usr/local/lib/python3.10/dist-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb) (0.6.0)\n",
      "Building wheels for collected packages: pypika\n",
      "  Building wheel for pypika (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
      "  Created wheel for pypika: filename=PyPika-0.48.9-py2.py3-none-any.whl size=53725 sha256=a0911caf03f1a1fb2bee859c6559ac0ac3a8d694fc2ae057d73bceecd2424b83\n",
      "  Stored in directory: /root/.cache/pip/wheels/e1/26/51/d0bffb3d2fd82256676d7ad3003faea3bd6dddc9577af665f4\n",
      "Successfully built pypika\n",
      "Installing collected packages: pypika, monotonic, mmh3, websockets, uvloop, ujson, python-multipart, python-dotenv, pypdf, overrides, orjson, opentelemetry-util-http, opentelemetry-proto, jsonpointer, humanfriendly, httptools, h11, dnspython, deprecated, chroma-hnswlib, bcrypt, backoff, asgiref, watchfiles, uvicorn, tiktoken, starlette, posthog, opentelemetry-exporter-otlp-proto-common, opentelemetry-api, jsonpatch, httpcore, email_validator, coloredlogs, opentelemetry-semantic-conventions, opentelemetry-instrumentation, onnxruntime, langsmith, kubernetes, httpx, opentelemetry-sdk, opentelemetry-instrumentation-asgi, openai, langchain-core, fastapi-cli, opentelemetry-instrumentation-fastapi, opentelemetry-exporter-otlp-proto-grpc, langchain-text-splitters, langchain_openai, fastapi, langchain, chromadb\n",
      "Successfully installed asgiref-3.8.1 backoff-2.2.1 bcrypt-4.1.3 chroma-hnswlib-0.7.3 chromadb-0.5.3 coloredlogs-15.0.1 deprecated-1.2.14 dnspython-2.6.1 email_validator-2.2.0 fastapi-0.111.0 fastapi-cli-0.0.4 h11-0.14.0 httpcore-1.0.5 httptools-0.6.1 httpx-0.27.0 humanfriendly-10.0 jsonpatch-1.33 jsonpointer-3.0.0 kubernetes-30.1.0 langchain-0.2.5 langchain-core-0.2.9 langchain-text-splitters-0.2.1 langchain_openai-0.1.9 langsmith-0.1.81 mmh3-4.1.0 monotonic-1.6 onnxruntime-1.18.0 openai-1.35.3 opentelemetry-api-1.25.0 opentelemetry-exporter-otlp-proto-common-1.25.0 opentelemetry-exporter-otlp-proto-grpc-1.25.0 opentelemetry-instrumentation-0.46b0 opentelemetry-instrumentation-asgi-0.46b0 opentelemetry-instrumentation-fastapi-0.46b0 opentelemetry-proto-1.25.0 opentelemetry-sdk-1.25.0 opentelemetry-semantic-conventions-0.46b0 opentelemetry-util-http-0.46b0 orjson-3.10.5 overrides-7.7.0 posthog-3.5.0 pypdf-4.2.0 pypika-0.48.9 python-dotenv-1.0.1 python-multipart-0.0.9 starlette-0.37.2 tiktoken-0.7.0 ujson-5.10.0 uvicorn-0.30.1 uvloop-0.19.0 watchfiles-0.22.0 websockets-12.0\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "try:\n",
    "    from google.colab import drive, userdata\n",
    "    COLAB = True\n",
    "    print(\"Note: using Google CoLab\")\n",
    "except:\n",
    "    print(\"Note: not using Google CoLab\")\n",
    "    COLAB = False\n",
    "\n",
    "# OpenAI Secrets\n",
    "if COLAB:\n",
    "    os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
    "\n",
    "# Install needed libraries in CoLab\n",
    "if COLAB:\n",
    "    !pip install langchain langchain_openai pypdf chromadb"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pC9A-LaYhsta"
   },
   "source": [
    "# 6.3: Understanding Embeddings\n",
    "\n",
    "An [embedding](https://platform.openai.com/docs/guides/embeddings) is a vector (list) of floating point numbers. The [distance](https://platform.openai.com/docs/guides/embeddings/which-distance-function-should-i-use) between two vectors measures their relatedness. Small distances suggest high relatedness, and large distances suggest low relatedness.\n",
    "\n",
    "The two embedding models that you will choose between in OpenAI are as follows:\n",
    "\n",
    "* text-embedding-3-small\n",
    "* text-embedding-3-large\n",
    "\n",
    "Choosing between the two OpenAI embedding models, \"text-embedding-3-small\" and \"text-embedding-3-large,\" depends on several factors related to your specific use case, including performance requirements, computational resources, and the nature of the tasks you need the embeddings for. Here are some key considerations:\n",
    "\n",
    "1. **Performance and Accuracy**:\n",
    " * **text-embedding-3-large**: Generally, larger models tend to capture more nuanced and complex relationships within the text, leading to better performance in tasks that require a deep understanding of language, such as semantic similarity, sentiment analysis, and more sophisticated NLP tasks.\n",
    " * **text-embedding-3-small**: Smaller models may not be as accurate or detailed as larger ones, but they can still perform well on many tasks, especially those with less complexity or when fine-tuned on specific datasets.\n",
    "2. **Computational Resources:**\n",
    " * **text-embedding-3-large**: Requires more computational power and memory. This requirement is important if you are deploying the model in a resource-constrained environment or need to process a large volume of data in real time.\n",
    " * **text-embedding-3-small**: More efficient resource usage, making it a better choice for applications with limited computational power or when operating at a scale where cost and speed are critical factors.\n",
    "3. **Latency and Throughput:**\n",
    " * **text-embedding-3-large**: Typically, larger models have higher latency due to their complexity, which might impact real-time applications.\n",
    " * **text-embedding-3-small**: Lower latency and faster inference times benefit applications requiring quick responses.\n",
    "4. **Cost:**\n",
    " * **text-embedding-3-large**: Likely to incur higher operational costs due to greater computational requirements.\n",
    " * **text-embedding-3-small**: More cost-effective, particularly for large-scale deployments.\n",
    " Use Case Specifics:\n",
    "\n",
    "The large model might be more appropriate for applications needing high precision and where detailed semantic understanding is crucial, such as nuanced text analysis or advanced AI research.\n",
    "The small model could be the better choice for applications where speed, cost, and efficiency are more critical, such as real-time systems, chatbots, or applications with more straightforward text processing needs.\n",
    "\n",
    "In summary,\n",
    "\n",
    "* Choose text-embedding-3-large if:\n",
    " * You need high accuracy and detailed semantic understanding.\n",
    " * You have sufficient computational resources and budget.\n",
    " * Latency is not a critical concern.\n",
    "* Choose text-embedding-3-small if:\n",
    " * You require efficient resource usage and lower costs.\n",
    " * You need faster inference times.\n",
    "\n",
    "The tasks are less complex, or the environment needs more resource-constrained.\n",
    "Evaluating your requirements and constraints will help you decide which model to use.\n",
    "\n",
    "## Instantiating an Embedding Model\n",
    "\n",
    "For this class, I suggest that you use **text-embedding-3-small**. It has all the capabilities that we need and will stretch your credits further. Let's begin by creating a client that utilizes this model.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "wP9LYwNUSKyv"
   },
   "outputs": [],
   "source": [
    "from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "embeddings_model = OpenAIEmbeddings(model=\"text-embedding-3-small\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "uGs1Fqk0Vu-6"
   },
   "source": [
    "Let's start by understanding the basics of an embedding model and vectors. An embedding model is a tool that can convert any text string, regardless of its length, into a vector. This vector is a unique representation of the text string. If the vectors for two text strings are the same, it means the text strings are identical. If the vectors are different, it suggests that the text strings are distinct. However, this difference is not a simple comparison. Even two very different text strings, with the same meaning, can produce similar vectors.\n",
    "\n",
    "To begin with, let's look at vectors for individual words."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "iq3WUSr7T1QK",
    "outputId": "fec4ed4c-f518-419a-ca1b-80995846fdb5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'list'>\n"
     ]
    }
   ],
   "source": [
    "l1 = embeddings_model.embed_query(\"dog\")\n",
    "l2 = embeddings_model.embed_query(\"Something that is a bit longer than a word.\")\n",
    "\n",
    "print(type(l1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wMqiJ6zFZUue"
   },
   "source": [
    "As you can see, the output is just a regular Python list. These lists are quite long."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "BNt_opHMX1B7",
    "outputId": "c0c21cc3-b5a0-4909-df01-b17351f4697a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1536\n",
      "1536\n"
     ]
    }
   ],
   "source": [
    "print(len(l1))\n",
    "print(len(l2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OrwZGIA-ZbK_"
   },
   "source": [
    "The length of this string will remain consistent across queries of the same model. The larger version of the model, while it does a better job of creating vectors that differentiate strings, doesn't always require a larger vector length to achieve this increase in quality. It's a nuanced concept worth exploring further.\n",
    "\n",
    "If we display the actual list itself, we can see that it is just a collection of numbers. Here, we display only the first ten elements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "MePSP-eGZpWA",
    "outputId": "e9a1e208-4f3c-4555-a3bb-fc99582c20ac"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.05113774910569191, -0.01870863139629364, -0.004298428073525429, 0.07271610200405121, -0.007174310740083456, -0.014693480916321278, -0.0059395902790129185, 0.005037412978708744, 0.018954960629343987, -0.01090618409216404]\n"
     ]
    }
   ],
   "source": [
    "print(l1[:10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "EtbM9gKCaqxU"
   },
   "source": [
    "## Comparing Vectors\n",
    "\n",
    "To compare these vectors we will use the mathematical capabilities of [Numpy](https://numpy.org/). There are multiple different approaches to compare vectors in mathematics, some of the most common are presented here.\n",
    "\n",
    "* **Dot Product**: Measures the cosine of the angle between two vectors, indicating their directional similarity. Used in determining orthogonality, projection, and in various applications like computer graphics and machine learning.\n",
    "\n",
    "* **Cross Product**: Computes a vector perpendicular to two given vectors in three-dimensional space, useful for finding the normal vector to a plane and in physics for torque and angular momentum calculations.\n",
    "\n",
    "* **Euclidean Distance**: Calculates the straight-line distance between two vectors, widely used in machine learning for clustering and nearest neighbor algorithms.\n",
    "\n",
    "* **Cosine Similarity**: Evaluates the cosine of the angle between two vectors, emphasizing the orientation rather than the magnitude, often used in text mining and information retrieval to compare document similarity.\n",
    "\n",
    "* **Manhattan Distance**: Measures the sum of absolute differences between the components of two vectors, useful in grid-based pathfinding algorithms and some machine learning applications.\n",
    "\n",
    "OpenAI [suggests](https://platform.openai.com/docs/guides/embeddings/frequently-asked-questions) that we use Cosine Similarity to compare their vectors, because it perserves the magnitude. The sign of the individual vector numbers is important, we do not want to discard it.\n",
    "\n",
    "We will begin by converting the two embeddings from Python lists to Numpy arrays."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "id": "xgKMSKraapv3"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Convert lists to numpy arrays\n",
    "vec1 = np.array(l1)\n",
    "vec2 = np.array(l2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UVyEFevXgCqR"
   },
   "source": [
    "OpenAI specifies that all their embeddings are normalized to length 1, often called [unit vectors](https://en.wikipedia.org/wiki/Unit_vector). This fact means that:\n",
    "\n",
    "* Cosine similarity can be computed slightly faster using just a dot product\n",
    "* Cosine similarity and Euclidean distance will result in the identical rankings\n",
    "\n",
    "Now, let's put this into practice. To verify that these embeddings are indeed of length 1, we can use a handy function. This function is specifically designed to analyze the length of a vector, making it a useful tool in our exploration of OpenAI embeddings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "0GPYq2wodpyJ"
   },
   "outputs": [],
   "source": [
    "def analyze_vector_length(vector):\n",
    "  # Calculate the length (norm) of the vector\n",
    "  length = np.linalg.norm(vector)\n",
    "\n",
    "  # Check if the vector is a unit vector\n",
    "  is_unit_vector = np.isclose(length, 1.0)\n",
    "\n",
    "  print(f\"Vector: {vector}\")\n",
    "  print(f\"Length of the vector: {length}\")\n",
    "  print(f\"Is the vector a unit vector? {'Yes' if is_unit_vector else 'No'}\")\n",
    "\n",
    "  # Normalize the vector to make it a unit vector\n",
    "  unit_vector = vector / length\n",
    "\n",
    "  # Verify the length of the normalized vector\n",
    "  normalized_length = np.linalg.norm(unit_vector)\n",
    "\n",
    "  print(f\"Normalized vector (unit vector): {unit_vector}\")\n",
    "  print(f\"Length of the normalized vector: {normalized_length}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "GRrx2dZhfrfG",
    "outputId": "f01bf3e7-0232-420e-942a-7ea191b45fff"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector: [ 0.05113775 -0.01870863 -0.00429843 ...  0.02879578  0.00215999\n",
      "  0.01790806]\n",
      "Length of the vector: 0.9999999997385218\n",
      "Is the vector a unit vector? Yes\n",
      "Normalized vector (unit vector): [ 0.05113775 -0.01870863 -0.00429843 ...  0.02879578  0.00215999\n",
      "  0.01790806]\n",
      "Length of the normalized vector: 1.0000000000000002\n"
     ]
    }
   ],
   "source": [
    "analyze_vector_length(vec1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "V459AdOAfqmd"
   },
   "source": [
    "Next, let's calculate the actual cosine similarity between our two vectors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "q2fgizuzXPAT",
    "outputId": "c6bbbd9d-7a90-403f-95be-4173bc206828"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cosine similarity: 0.16737720056743507\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Convert lists to numpy arrays\n",
    "vec1 = np.array(l1)\n",
    "vec2 = np.array(l2)\n",
    "\n",
    "# Calculate the dot product\n",
    "dot_product = np.dot(vec1, vec2)\n",
    "\n",
    "# Calculate the magnitudes (L2 norms)\n",
    "magnitude_vec1 = np.linalg.norm(vec1)\n",
    "magnitude_vec2 = np.linalg.norm(vec2)\n",
    "\n",
    "# Calculate cosine similarity\n",
    "cosine_similarity = dot_product / (magnitude_vec1 * magnitude_vec2)\n",
    "\n",
    "print(f\"Cosine similarity: {cosine_similarity}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NO3zv6WGhDEw"
   },
   "source": [
    "The denominator above is 1.0, as shown here. This is due to the unit vector property. We can simplify our unit vector comparison to just the dot product, as indicated by OpenAI."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "eMKiwXR7hGl1",
    "outputId": "546a02e8-55f4-4ad7-b9d0-d037a78354be"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0000000478179367\n"
     ]
    }
   ],
   "source": [
    "print(magnitude_vec1 * magnitude_vec2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1weLEmSEhfvY"
   },
   "source": [
    "The dot product can be calculated as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "2c981Unthh5h",
    "outputId": "75df5174-178e-4072-b8c8-00f8fdcb1597"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.16737720857106747\n"
     ]
    }
   ],
   "source": [
    "print(np.dot(vec1, vec2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "rzGVjgjbkeOC"
   },
   "source": [
    "## Evaluating Similarities of Strings\n",
    "\n",
    "We will begin by creating a simple function to compare two strings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "id": "ooX8bTmFlFRJ"
   },
   "outputs": [],
   "source": [
    "def compare_str(embeddings_model, text1, text2):\n",
    "    \"\"\"\n",
    "    This function returns the dot product of embeddings for two given text strings.\n",
    "\n",
    "    Parameters:\n",
    "    embeddings_model: The embeddings model to use for generating embeddings.\n",
    "    text1 (str): The first text string.\n",
    "    text2 (str): The second text string.\n",
    "\n",
    "    Returns:\n",
    "    float: The dot product of the embeddings for text1 and text2.\n",
    "    \"\"\"\n",
    "    # Get the embeddings for the two text strings\n",
    "    embedding1 = embeddings_model.embed_query(text1)\n",
    "    embedding2 = embeddings_model.embed_query(text2)\n",
    "\n",
    "    # Convert embeddings to numpy arrays for dot product calculation\n",
    "    embedding1_array = np.array(embedding1)\n",
    "    embedding2_array = np.array(embedding2)\n",
    "\n",
    "    # Calculate and return the dot product\n",
    "    dot_product = np.dot(embedding1_array, embedding2_array)\n",
    "    return dot_product"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "20QUKA9-qa4J"
   },
   "source": [
    "Lets try it with two descriptions of a \"lawn mower\" that do not use many similar words."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "4dgEmHV2lHEs",
    "outputId": "6553350e-fb4b-48e0-e8cf-6dec3fa43657"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.6298291212454291"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "compare_str(embeddings_model,\n",
    "            \"A machine that helps people to cut grass.\",\n",
    "            \"Device with blades to cut plants under it.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "etJISf2MmcY1"
   },
   "source": [
    "The value of the dot product (cosine similarity) ranges from -1 to 1. We can interpret it as follows:\n",
    "\n",
    "* High Similarity: Values close to 1 indicate high similarity.\n",
    "* Low Similarity: Values close to -1 indicate high dissimilarity.\n",
    "* Neutral/No Similarity: Values close to 0 indicate no apparent similarity.\n",
    "\n",
    "So a value of 0.62 means they are reasonably similar. Lets adjust it to compare a lawn mower to an airplane."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "b43n_JgOlWD1",
    "outputId": "429def1f-dbaf-43c8-8b66-c819319451be"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.2694946993627674"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "compare_str(embeddings_model,\n",
    "            \"A machine that helps people to cut grass.\",\n",
    "            \"Vehicle that flys through the air.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "IBucz-M7nxp3"
   },
   "source": [
    "We can see the similarity is lower."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3.11 (genai)",
   "language": "python",
   "name": "genai"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
